/*
 * Copyright 2002-2007 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package uk.co.foocode.sample.petclinic.web.view;

import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.time.StopWatch;
import org.springframework.web.servlet.view.AbstractView;

import net.sf.json.JSON;
import net.sf.json.JSONSerializer;
import net.sf.json.JsonConfig;
import net.sf.json.util.CycleDetectionStrategy;
import net.sf.json.util.PropertyFilter;

/**
 * A View that renders its model as a JSON object.
 *
 * @author Andres Almiray <aalmiray@users.sourceforge.net>
 */
public class JsonView extends AbstractView {
   /** Default content type. Overridable as bean property. */
   private static final String DEFAULT_JSON_CONTENT_TYPE = "application/json";
   /** Set of properties to be excluded in the JSON rendering. */
   private String[] excludedProperties;
   /**
    * Tells the JSONSerializer to ignore or not its internal property
    * exclusions.
    */
   private boolean ignoreDefaultExcludes;

   public JsonView() {
      super();
      setContentType( DEFAULT_JSON_CONTENT_TYPE );
   }

   /**
    * Returns whether the JSONSerializer will ignore or not its internal property
    * exclusions.
    */
   public boolean isIgnoreDefaultExcludes() {
      return ignoreDefaultExcludes;
   }

   /**
    * Sets the group of properties to be excluded.
    */
   public void setExcludedProperties( String[] excludedProperties ) {
      this.excludedProperties = excludedProperties;
   }

   /**
    * Sets whether the JSONSerializer will ignore or not its internal property
    * exclusions.
    */
   public void setIgnoreDefaultExcludes( boolean ignoreDefaultExcludes ) {
      this.ignoreDefaultExcludes = ignoreDefaultExcludes;
   }

   /**
    * Creates a JSON [JSONObject,JSONArray,JSONNUll] from the model values.
    */
   protected JSON createJSON( Map model, HttpServletRequest request, HttpServletResponse response ) {
      return defaultCreateJSON( model );
   }

   /**
    * Creates a JSON [JSONObject,JSONArray,JSONNUll] from the model values.
    */
   protected final JSON defaultCreateJSON( Map model ) {
	   JsonConfig jsonConfig = new JsonConfig();
	   jsonConfig.setIgnoreDefaultExcludes(isIgnoreDefaultExcludes());
	   jsonConfig.setExcludes(getExcludedProperties());
	   jsonConfig.setCycleDetectionStrategy(CycleDetectionStrategy.LENIENT);
	   jsonConfig.setJavaPropertyFilter(new PropertyFilter() {
		public boolean apply(Object source, String name, Object value) {
			return !"propertyAccessor".equals(name);
		}});
      return JSONSerializer.toJSON( model, jsonConfig);
   }

   /**
    * Returns the group of properties to be excluded.
    */
   protected String[] getExcludedProperties() {
      return excludedProperties;
   }

   protected void renderMergedOutputModel( Map model, HttpServletRequest request,
         HttpServletResponse response ) throws Exception {
	   StopWatch watch = new StopWatch();
	   watch.start();
      response.setContentType( getContentType() );
      JSON json = createJSON( model, request, response );
      json.write( response.getWriter() );
      watch.stop();
      logger.debug("JSON Rendering took "+watch.getTime() + " ms");
   }
}